// Input code from: <http://www.directxtutorial.com/Tutorial9/E-DirectInput/dx9E1.aspx#still>
// Mouse picking code from: <http://www.mvps.org/directx/articles/improved_ray_picking.htm>

#include "winmain.h"

/*****************************
Function Prototypes
*****************************/
bool initWindow(HINSTANCE hInst, int, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
bool initWindowDetail(void);
void render(void);
void beginRender(void);
void endRender(void);
void shutdown(void);
void initDInput(HINSTANCE, HWND);						// sets up and initializes DirectInput
void detectDInput(void);								// gets the current input state
void cleanDInput(void);									// closes DirectInput and releases memory
void initializeOptionLists(void);
void setDefaultFiles(void);
void setSoundFiles(void);
void initializeObjectLists(void);
void drawUnitList(void);
void drawEnvList(void);
void drawMiscList(void);
void drawSelectedObject(void);
void drawMap(void);
void drawObjects(void);

/*****************************
WinMain
*****************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {

	AllocConsole();
	HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord = {100,120};
	SetConsoleScreenBufferSize(console, coord);
	_cprintf("-- Debugger --\n\n");

	RECT window;
	window.left = INITIAL_WINDOW_POSITION_X;
	window.top = INITIAL_WINDOW_POSITION_Y;
	window.right = window.left + screenWidth;
	window.bottom = window.top + screenHeight;
	if(!AdjustWindowRect(&window, WS_SYSMENU | WS_THICKFRAME | WS_CAPTION |WS_VISIBLE, false)) {
		MessageBox(0, "Unable to Adjust Window!", "ERROR", MB_OK);
		return false;
	}

	if(!initWindow(hInstance, window.right - window.left, window.bottom - window.top)) {
		MessageBox(NULL, "Unable to Create Window", "ERROR", MB_OK);
		return false;
	}

	if(!initWindowDetail()) {
		MessageBox(NULL, "Unable to Create Device", "ERROR", MB_OK);
		return false;
	}

	initDInput(hInstance, wHandle);

	hr = D3DXCreateFont(pd3dDevice,					//D3D Device
						FONT_HEIGHT,				//Font height
						0,							//Font width
						FONT_WEIGHT,				//Font Weight
						1,							//Mip Levels
						false,						//Italic
						DEFAULT_CHARSET,			//CharSet
						OUT_DEFAULT_PRECIS,			//OutputPrecision
						ANTIALIASED_QUALITY,		//Quality
						DEFAULT_PITCH|FF_DONTCARE,	//Pitch And Family
						"Arial",					//Face Name
						&standardFont);				//Font

	if(FAILED(hr)) {
		MessageBox(NULL, "Unable to Set Font", "ERROR", MB_OK);
		return 0;
	}

	D3DXCreateSprite(pd3dDevice, &sprite);
	
	initializeOptionLists();
	setDefaultFiles();
	setSoundFiles();
	initializeObjectLists();
	setOpenFilesList();
	loadUIGraphics(0, 5, 0, 600, 0, 200, screenWidth - MODEL_ICON_WIDTH, 100, 
				(screenWidth/2) - (OPEN_MAP_BOX_WIDTH/2), (screenHeight/2) - (OPEN_MAP_BOX_HEIGHT/2));

	createCamera(NEAR_CLIP, FAR_CLIP);	// near clip plane, far clip plane
	moveCamera(INITIALIZE_CAMERA_MOVE);
	pointCamera(INITIALIZE_CAMERA_POINT);

	initializeLight();

	bool once = false;

	MSG msg;
	ZeroMemory(&msg, sizeof(msg));
	while(msg.message != WM_QUIT) {
		if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) {
			TranslateMessage(&msg);
            DispatchMessage(&msg);
		} else {
			detectDInput();
			handleInput();
			now = clock();
			now2 = clock();

			update();

			render();

			Sleep(10);
		}
    }

	sprite->Release();

	shutdown();

	return (int)msg.wParam;
}

/*****************************
InitWindow
*****************************/
bool initWindow(HINSTANCE hInstance, int width, int height)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= 0;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= "World Editor";
	wcex.hIconSm		= 0;
	RegisterClassEx(&wcex);

	wHandle = CreateWindowExA(0, "World Editor", "World Editor", NormalWindow, INITIAL_WINDOW_POSITION_X, 
							INITIAL_WINDOW_POSITION_Y, width, height, NULL, NULL, hInstance, NULL);
	
	if(!wHandle)
		return false;

	ShowWindow(wHandle, SW_SHOW);
	UpdateWindow(wHandle);

	return true;
}

/*****************************
WndProc
*****************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) 
	{
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

/*****************************
shutdown
*****************************/
void shutdown(void) {
	if( pd3dDevice != NULL) {
        pd3dDevice->Release();
		pd3dDevice = NULL;
	}
    if( pD3D != NULL){
        pD3D->Release();
		pD3D = NULL;
	}
	if(!MenuButtons.empty()) {
		list<BUTTON>::iterator iter = MenuButtons.begin();
		for(;iter != MenuButtons.end(); iter++) {
			iter->pic->Release();
		}
	}
	if(!UnitButtons.empty()) {
		list<BUTTON>::iterator iter = UnitButtons.begin();
		for(;iter != UnitButtons.end(); iter++) {
			iter->pic->Release();
		}
	}
	if(!EnvButtons.empty()) {
		list<BUTTON>::iterator iter = EnvButtons.begin();
		for(;iter != EnvButtons.end(); iter++) {
			iter->pic->Release();
		}
	}
	if(!MiscButtons.empty()) {
		list<BUTTON>::iterator iter = MiscButtons.begin();
		for(;iter != MiscButtons.end(); iter++) {
			iter->pic->Release();
		}
	}
	if(!LightButtons.empty()) {
		list<BUTTON>::iterator iter = LightButtons.begin();
		for(;iter != LightButtons.end(); iter++) {
			iter->pic->Release();
		}
	}
	cleanDInput();
	delete MapObject;
}

/*****************************
initWindowDetail
*****************************/
bool initWindowDetail(void) {
	if(NULL == (pD3D = Direct3DCreate9(D3D_SDK_VERSION))) {
		return false;
	}

	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.Windowed					= TRUE;
	d3dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat			= D3DFMT_UNKNOWN;
	d3dpp.BackBufferCount			= 1;
	d3dpp.BackBufferHeight			= screenHeight;
	d3dpp.BackBufferWidth			= screenWidth;
	d3dpp.hDeviceWindow				= wHandle;
	d3dpp.AutoDepthStencilFormat	= D3DFMT_D16;
	d3dpp.EnableAutoDepthStencil	= TRUE;

	if(antialising) {
		DWORD total;
		if(SUCCEEDED(pD3D->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
														d3dpp.AutoDepthStencilFormat, true,
														D3DMULTISAMPLE_NONMASKABLE,
														&total))) {
			d3dpp.MultiSampleType = D3DMULTISAMPLE_NONMASKABLE;
			d3dpp.MultiSampleQuality = total - 1;
			_cprintf("Antialising [ON]: x%i\n", d3dpp.MultiSampleQuality);
		} else {
			MessageBox(NULL, "Unable to antialise", "ERROR", MB_OK);
			return false;
		}
	} else {
		_cprintf("Antialising [OFF]\n");
	}

	if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, wHandle,
								D3DCREATE_HARDWARE_VERTEXPROCESSING,
								&d3dpp, &pd3dDevice))) {
		return false;
	}

	if(antialising) {
		pd3dDevice->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, TRUE);
		pd3dDevice->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, TRUE);
	}

	pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
	pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
	pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
	pd3dDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
	pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);

	// Turn on culling
	pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	// Enable alpha blending
	pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

	// Light settings
	pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
	pd3dDevice->SetRenderState(D3DRS_DITHERENABLE, TRUE);
	pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);
	pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);

	// new, dont know how they will affect the program
	pd3dDevice->SetSamplerState(0, D3DSAMP_MAXANISOTROPY, 8);					// anisotropic level
	pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);		// minification
	pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);			// magnification
	pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);			// mipmap

	return true;
}

/*****************************
Render Functions
*****************************/
void render(void) {
	beginRender();
	
	if(displayMap)
		drawMap();
	//drawObjects();
	if(displayEnvList)
		drawEnvList();
	if(displayUnitList)
		drawUnitList();
	if(displayMiscList)
		drawMiscList();

	drawSelectedObject();
	drawUI();

	endRender();
}

void beginRender(void) {
	if(NULL == pd3dDevice)
		return;

	pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(100,100,100), 1.0f, 0);
	pd3dDevice->BeginScene();
}

void endRender(void) {
	pd3dDevice->EndScene();
    pd3dDevice->Present(NULL, NULL, NULL, NULL);
}

/*****************************
DirectInput Functions
*****************************/
void initDInput(HINSTANCE hInstance, HWND hWnd) {
    // create the DirectInput interface
    DirectInput8Create(hInstance,					// the handle to the application
                       DIRECTINPUT_VERSION,			// the compatible version
                       IID_IDirectInput8,			// the DirectInput interface version
                       (void**)&dInput,				// the pointer to the interface
                       NULL);

    // create the keyboard device
    dInput->CreateDevice(GUID_SysKeyboard,			// the default keyboard ID being used
                      &dKeyboard,					// the pointer to the device interface
                      NULL);
	dInput->CreateDevice(GUID_SysMouse,
                      &dMouse,
                      NULL);

	// set the data formats
	dKeyboard->SetDataFormat(&c_dfDIKeyboard);
	dMouse->SetDataFormat(&c_dfDIMouse2);

	// set the cooperative levels
    dKeyboard->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
	dMouse->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
}

void detectDInput(void) {
    // get access if we don't have it already
    dKeyboard->Acquire();
	dMouse->Acquire();

    // get the input data
    dKeyboard->GetDeviceState(256, (LPVOID)dKey);
	dMouse->GetDeviceState(sizeof(DIMOUSESTATE2), (LPVOID)&mouseState);
}

void cleanDInput(void) {
    dKeyboard->Unacquire();				// make sure the keyboard is unacquired
	dMouse->Unacquire();				// make sure the mouse in unacquired
    dInput->Release();					// close DirectInput before exiting
}

void initializeOptionLists(void) {
	AlignmentList.push_back("Neutral");
	AlignmentList.push_back("Player");
	AlignmentList.push_back("Ally");
	AlignmentList.push_back("Enemy");
	AlignmentList.push_back("Sound(Trigger)");
	AlignmentList.push_back("Sound(Auto)");

	CanMoveList.push_back("True");
	CanMoveList.push_back("False");

	StanceList.push_back("Aggressive");
	StanceList.push_back("Defensive");
	StanceList.push_back("Passive");

	PurposeList.push_back("Win");
	PurposeList.push_back("Lose");
	PurposeList.push_back("Spawn(1Sec)");
	PurposeList.push_back("Spawn(5Sec)");
	PurposeList.push_back("Spawn(10Sec)");
	PurposeList.push_back("Spawn(20Sec)");
	PurposeList.push_back("Bombard");
	PurposeList.push_back("Attack");
	PurposeList.push_back("Defend");
	PurposeList.push_back("PlayerStart");
	PurposeList.push_back("MapCornerTL");
	PurposeList.push_back("MapCornerTR");
	PurposeList.push_back("MapCornerBL");
	PurposeList.push_back("MapCornerBR");
	PurposeList.push_back("Sound(Loop)");
	PurposeList.push_back("Sound(Once)");
}

void setDefaultFiles(void) {
	WIN32_FIND_DATA fileData;
	HANDLE h;
	bool finished = false;

	h = FindFirstFile(TEXT(defaultObjectFilesSearch.c_str()), &fileData);
	DefaultFilesList.push_back(fileData.cFileName);
	
	while(!finished) {
		if(FindNextFile(h, &fileData)) {
			DefaultFilesList.push_back(fileData.cFileName);
		} else if(!FindNextFile(h, &fileData)) {
			finished = TRUE;
		}
	}

	if(!FindClose(h)) {
		_cprintf("ERROR: Unable to close search handle\n");
	}
}

void setSoundFiles(void) {
	WIN32_FIND_DATA fileData;
	HANDLE h;
	bool finished = false;

	h = FindFirstFile(TEXT(soundFilesSearch.c_str()), &fileData);
	SoundFilesList.push_back(fileData.cFileName);
	
	while(!finished) {
		if(FindNextFile(h, &fileData)) {
			SoundFilesList.push_back(fileData.cFileName);
		} else if(!FindNextFile(h, &fileData)) {
			finished = TRUE;
		}
	}

	if(!FindClose(h)) {
		_cprintf("ERROR: Unable to close search handle\n");
	}
}

void initializeObjectLists(void) {
	string objectType;
	string objectDataFile;
	string objectLocation;
	string objectIconLocation;
	string objectName;
	string objectFunction;
	//int order = 0;

	list<string>::iterator listIter = DefaultFilesList.begin();
	for(;listIter != DefaultFilesList.end(); listIter++) {
		string tempLocation = defaultFilesLocation;
		tempLocation.append(*listIter);
		
		fstream in(tempLocation.c_str());
		in >> objectType;
		
		if(objectType == "UNIT_OBJECT") {
			in >> objectDataFile;
			in >> objectLocation;
			in >> objectIconLocation;
			in >> objectName;

			D3DXMATRIX tMatrix;
			D3DXMatrixTranslation(&tMatrix, 0.0f, 0.0f, 0.0f);

			list<string>::iterator a = AlignmentList.begin();
			list<string>::iterator cMove = CanMoveList.begin();
			list<string>::iterator st = StanceList.begin();
			
			Unit tempUnit(objectLocation, objectDataFile, objectIconLocation, objectName, *a, tMatrix, 0, *cMove, *st);
			if(!tempUnit.initializeObject(pd3dDevice)) {
				_cprintf("ERROR: Could not load unit mesh!\n");
			}
			UnitObjects.push_front(tempUnit);
		}

		if(objectType == "ENV_OBJECT") {
			in >> objectDataFile;
			in >> objectLocation;
			in >> objectIconLocation;
			in >> objectName;

			D3DXMATRIX tMatrix;
			D3DXMatrixTranslation(&tMatrix, 0.0f, 0.0f, 0.0f);

			list<string>::iterator a = AlignmentList.begin();
			
			Env tempEnv(objectLocation, objectDataFile, objectIconLocation, objectName, *a, tMatrix, 0);
			if(!tempEnv.initializeObject(pd3dDevice)) {
				_cprintf("ERROR: Could not load env mesh!\n");
			}
			EnvObjects.push_front(tempEnv);
		}

		if(objectType == "MISC_OBJECT") {
			in >> objectDataFile;
			in >> objectLocation;
			in >> objectIconLocation;
			in >> objectName;
			in >> objectFunction;

			D3DXMATRIX tMatrix;
			D3DXMatrixTranslation(&tMatrix, 0.0f, 0.0f, 0.0f);

			list<string>::iterator a = AlignmentList.begin();
			list<string>::iterator pur = PurposeList.begin();
			
			Misc tempMisc(objectLocation, objectDataFile, objectIconLocation, objectName, *a, tMatrix, *pur, 5, 0, objectFunction);
			if(!tempMisc.initializeObject(pd3dDevice)) {
				_cprintf("ERROR: Could not load misc mesh!\n");
			}
			MiscObjects.push_front(tempMisc);
		}

		if(objectType == "MAP_OBJECT") {
			objectDataFile = "NONE";
			objectIconLocation = "NONE";
			in >> objectLocation;
			in >> objectName;
			in >> mapScaleX;
			in >> mapScaleY;
			in >> mapScaleZ;

			D3DXMATRIX tMatrix;
			D3DXMatrixTranslation(&tMatrix, 0.0f, 0.0f, 0.0f);

			MapObject = new Object(objectLocation, objectDataFile, objectIconLocation, objectName, "Neutral", tMatrix);
			if(!MapObject->initializeObject(pd3dDevice)) {
				_cprintf("ERROR: Could not load map mesh!\n");
			}
			MapObject->scaleObject((float)mapScaleX,(float)mapScaleY,(float)mapScaleZ);
		}

		in.close();
	}
}

void drawUnitList(void) {
	list<Unit>::iterator unitIter = UnitList.begin();
	for(;unitIter != UnitList.end(); unitIter++) {
		unitIter->drawMesh(pd3dDevice);
	}
}

void drawEnvList(void) {
	list<Env>::iterator envIter = EnvList.begin();
	for(;envIter != EnvList.end(); envIter++) {
		envIter->drawMesh(pd3dDevice);
	}
}

void drawMiscList(void) {
	list<Misc>::iterator miscIter = MiscList.begin();
	for(;miscIter != MiscList.end(); miscIter++) {
		miscIter->drawMesh(pd3dDevice);
	}
}

void drawSelectedObject(void) {
	if(cState == UNIT_SELECTED)
		selectedUnit->drawMesh(pd3dDevice);
	if(cState == ENV_SELECTED)
		selectedEnv->drawMesh(pd3dDevice);
	if(cState == MISC_SELECTED)
		selectedMisc->drawMesh(pd3dDevice);
}

void drawMap(void) {
	MapObject->drawMesh(pd3dDevice);
}

void drawObjects(void) {
	list<Unit>::iterator uI = UnitList.begin();
	list<Env>::iterator eI = EnvList.begin();
	list<Misc>::iterator mI = MiscList.begin();
	list<char>::iterator cIter = drawingSequenceType.begin();

	int i=0;
	for(;cIter != drawingSequenceType.end(); cIter++) {
		if(*cIter == 'U' && uI != UnitList.end()) {
			uI->drawMesh(pd3dDevice);
			uI++;
		}
		if(*cIter == 'E' && eI != EnvList.end()) {
			eI->drawMesh(pd3dDevice);
			eI++;
		}
		if(*cIter == 'M' && mI != MiscList.end()) {
			mI->drawMesh(pd3dDevice);
			mI++;
		}
		i++;
	}
}